﻿/***************************************************COPYRIGHT***************************************
* Copyright (c) 2008, Whiteboard-IT
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
* the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following 
 * disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the distribution.
*
* Neither the name of Whiteboard-IT, LLC nor the names of its contributors may be used to endorse or promote 
 * products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*****************************************************************************************************************/
using System;
using System.Data;
using Boo.Lang;

namespace Mite
{
	public class NVarCharColumn : Column
	{
		public NVarCharColumn(string name, uint length) : base(name, DbType.String)
		{
			Name = name;
			Length = length;
		}
	}

	public class Column : ICloneable
	{
		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		public Column()
		{
		}

		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		public Column(Hash options): this("", DbType.String, options)
		{
		}

		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		public Column(string name, DbType dataType): this(name, dataType, new Hash())
		{
		}

		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		public Column(string name, Hash options): this(name, DbType.String, options)
		{
		}

		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		public Column(DbType dbType, Hash options): this("", dbType, options)
		{
		}

		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		public Column(string name, DbType dataType, Hash options): this(name, dataType, ColumnAttributes.None, options)
		{
		}

		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		public Column(string name, DbType dataType, ColumnAttributes attributes): this(name, dataType, attributes, null)
		{
		}

		/// <summary>
		/// Creates an instance of Column.
		/// </summary>
		/// <exception cref="ArgumentNullException">Name can not be null.</exception>
		public Column(string name, DbType dataType, ColumnAttributes attributes, Hash options)
		{
			Guard.StringArgumentNotNullOrEmpty("name", name);

			Name = name;
			DataType = dataType;
			this.attributes = attributes;

			if (options != null ){
				foreach (object key in options.Keys)
				{
					switch (key.ToString().ToLower())
					{
						case "primary":
							if (options[key].ToBool())
								PrimaryKey();
							break;
						case "identity":
							if (options[key].ToBool())
								Identity();
							break;
						case "name":
							Name = options[key].ToString();
							break;
						case "nullable":
							IsNullable = options[key].ToBool();
							break;
						case "max_length":
						case "length":
							Length = (uint) options[key].ToInt();
							break;
						case "precision":
							Precision = (uint) options[key].ToInt();
							break;
						case "scale":
							Scale = (uint) options[key].ToInt();
							break;
						case "data_type":
						case "datatype":
							DataType = (DbType) options[key];
							break;
					}
				}
			}
		}

		public Table Table { get; set; }

		public string Name { get; set; }

		public DbType DataType { get; set; }

		public bool IsIdentity
		{
			get { return (attributes & ColumnAttributes.Identity) == ColumnAttributes.Identity; }
		}

		/// <summary>
		/// Identity specification of the column.
		/// </summary>
		public IdentitySpecification IdentitySpecification { get; private set; }

		public bool IsForeignKey
		{
			get { return References != null; }
		}

		public bool IsReplicated
		{
			get { return false; //TODO: Implement Replication
			}
		}

		/// <summary>
		/// Returns <c>true</c> if the column can hold a null value and <c>false</c> if it can not.
		/// Use the setter with care because making a column nullable will automatically remove 
		/// identity, primary key, and row unique identifier constraints.
		/// </summary>
		public bool IsNullable
		{
			get { return attributes == ColumnAttributes.None; }
			set
			{
				if (value)
					attributes = ColumnAttributes.None;
				else
					attributes = ColumnAttributes.NotNull;
			}
		}

		public bool IsPrimaryKey { get { return (attributes & ColumnAttributes.PrimaryKey) == ColumnAttributes.PrimaryKey; } }

		public uint? Length { get; set; }

		/// <summary>
		/// Maximum total number of decimal digits that can be stored.
		/// This includes digits on both sides of the decimal point.
		/// </summary>
		public uint? Precision { get; set; }

		/// <summary>
		/// Maximum number of digits that can be stored to the right of the decimal point.
		/// </summary>
		public uint? Scale { get; set; }

		/// <summary>
		/// Unique column level constraint.
		/// </summary>
		public bool IsUnique { get; set; }

		/// <summary>
		/// Default value of the column if not set.
		/// </summary>
		public object Default { get; set; }

		/// <summary>
		/// ColumnAttributes of the column.
		/// </summary>
		public ColumnAttributes Attributes { get { return attributes; } }

		/// <summary>
		/// Make this column into a primary key.
		/// </summary>
		public void PrimaryKey()
		{
			attributes |= ColumnAttributes.PrimaryKey;
		}

		/// <summary>
		/// Make this column into an identity column.
		/// </summary>
		public void Identity()
		{
			Identity(1, 1);
		}

		/// <summary>
		/// Make this column into an identity column.
		/// </summary>
		public void Identity(uint seed, uint increment)
		{
			attributes = attributes | ColumnAttributes.Identity;
			IdentitySpecification = new IdentitySpecification { Seed = seed, Increment = increment };
		}

		/// <summary>
		/// Remove identity specification from this column.
		/// </summary>
		public void RemoveIdentity()
		{
			attributes = attributes ^ ColumnAttributes.Identity;
		}

		/// <summary>
		/// <see cref="Column"/> that this column references as a foreign key.
		/// <c>Null</c> if this column is not a foreign key.
		/// </summary>
		public Column References
		{
			get { return references; }
			private set { references = value; }
		}

		public string DataTypeString()
		{
			// TODO: Remove this method.
			string precisionscale = (Scale == 0 ? Precision.ToString() : Precision + "," + Scale);
			return String.Format("{0}({1})", DataType, precisionscale);
		}

		public object Clone()
		{
			return new Column(Name, DataType, attributes)
			       {
			       	Table = Table,
			       	Length = Length,
			       	Precision = Precision,
			       	IsUnique = IsUnique,
			       	References = References
			       };
		}

		public override string ToString()
		{
			return Name;
		}

		public override bool Equals(object obj)
		{
			if (obj == null)
				return false;

			var c = obj as Column;
			if (c == null)
				return false;

			if (c.Table == null &&
			    Table == null)
				return c.Name.Equals(Name);

			if (c.Table == null &&
			    Table != null)
				return false;

			if (c.Table != null &&
			    Table == null)
				return false;

			return c.Name.Equals(Name) && c.Table.Equals(Table);
		}

		public override int GetHashCode()
		{
			if (Table == null)
				return Name.GetHashCode();
			return (Name + Table.Name).GetHashCode();
		}

		/// <summary>
		/// Reference <paramref name="columnToReference"/>.
		/// This column becomes a foreign key to <paramref name="columnToReference"/>.
		/// </summary>
		/// <param name="columnToReference">Column to reference.</param>
		public void Reference(Column columnToReference)
		{
			references = columnToReference;
		}

		public Column CloneColumn()
		{
			return Clone() as Column;
		}

		protected bool isIdentity;
		protected bool isIndex;
		protected bool isNullable;
		private ColumnAttributes attributes;
		private Column references;
	}

	[Flags]
	public enum ColumnAttributes
	{
		None = 0,
		NotNull = 2,
		Identity = 4 | NotNull,
		PrimaryKey = 8 | NotNull,
		RowUniqueIdentifier = 16 | NotNull
	}

	/// <summary>
	/// Specifies the seed and increment of an identity column.
	/// </summary>
	public struct IdentitySpecification
	{
		/// <summary>
		/// Seed of the identity.
		/// </summary>
		public uint Seed { get; set; }

		/// <summary>
		/// Increment of the identity.
		/// </summary>
		public uint Increment { get; set; }
	}
}